package top.linyuxb.lycloud.common.utils;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import top.linyuxb.lycloud.common.common.LyConfigServerService;
import top.linyuxb.lycloud.common.constant.CommunicationTypeEnum;
import top.linyuxb.lycloud.common.dto.LyRpcServerDto;
import top.linyuxb.lycloud.common.po.LyConfigServerPo;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by Intellij IDEA.
 *
 * @Author: linyuxb
 * @Date: 2024/6/6 15:36
 * @Desc: rpc负载均衡器
 * https://www.jb51.net/article/225909.htm#_lab2_0_0
 */
@Slf4j
public class LyRpcRibbonUtil {
    /**
     * 存放服务信息
     */
    private static final List<LyRpcServerDto> servers = new ArrayList<>();
    /**
     * 存放注册中心，服务信息 缓存5分钟
     */
    private static final Cache<String, List<LyRpcServerDto>> cache = CacheUtil.newLRUCache(100, 5 * 60 * 1000);
    /**
     * 存放加权服务地址
     */
    private static final List<String> ipArrayList = new ArrayList<>();
    /**
     * 通讯类型
     */
    private static String TYPE = "";
    /**
     * 服务信息存放
     */
    private static String SERVER_INFO = "";
    /**
     * 轮询指针
     */
    private static int number = 0;
    /**
     * 加权轮询指针
     */
    private static int weightNumber = 0;

    /**
     * 初始化服务 返回服务数
     *
     * @param serverInfo
     * @return
     */
    public static Integer initServer(String type, String serverInfo) {
        if (StrUtil.isEmpty(TYPE)) {
            TYPE = type;
        }
        if (StrUtil.isEmpty(SERVER_INFO)) {
            SERVER_INFO = serverInfo;
        }
        if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), type)) {
            if (CollUtil.isEmpty(servers)) {
                Lock lock = new ReentrantLock();
                try {
                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                        try {
                            if (CollUtil.isEmpty(servers)) {
                                Stream.of(serverInfo.split(";"))
                                        .map(info -> {
                                            if (info.contains(",")) {
                                                String[] split = info.split(",");
                                                return LyRpcServerDto.builder()
                                                        .server(split[0])
                                                        .weight(Integer.valueOf(split[1]))
                                                        .build();
                                            }
                                            return LyRpcServerDto.builder()
                                                    .server(info)
                                                    .weight(10)
                                                    .build();
                                        })
                                        .forEach(servers::add);
                            }
                            if (CollUtil.isEmpty(ipArrayList)) {
                                for (LyRpcServerDto server : servers) {
                                    for (int i = 0; i < server.getWeight(); i++) {
                                        ipArrayList.add(server.getServer());
                                    }
                                }
                            }
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    log.error("RoundRobin InterruptedException", e);
                }
            }
        }
        if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), type)) {
            if (CollUtil.isEmpty(servers)) {
                Lock lock = new ReentrantLock();
                try {
                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                        try {
                            if (CollUtil.isEmpty(servers)) {
                                Stream.of(serverInfo.split(";"))
                                        .map(info -> {
                                            String[] split = info.split(",");
                                            return LyRpcServerDto.builder()
                                                    .app(split[0])
                                                    .env(split[1])
                                                    .build();
                                        })
                                        .forEach(servers::add);
                            }
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    log.error("RoundRobin InterruptedException", e);
                }
            }
        }
        return servers.size();
    }

    /**
     * 检查服务
     */
    private static void checkServer() {
        if (CollUtil.isEmpty(servers)) {
            for (int i = 0; i < 5; i++) {
                Integer i1 = initServer(TYPE, SERVER_INFO);
                if (i1 > 0) {
                    return;
                }
            }
        }
    }

    /**
     * 服务刷新，清空指定注册的服务
     *
     * @param app
     * @param env
     */
    public static void cleanRegisterServer(String app, String env) {
        cache.remove(app + "_" + env);
    }

    /**
     * 获取配置服务
     *
     * @return
     */
    private static List<LyRpcServerDto> getRegisterServer(String app, String env) {
        List<LyRpcServerDto> lyRpcServerDtos = cache.get(app + "_" + env);
        if (CollUtil.isNotEmpty(lyRpcServerDtos)) {
            return new ArrayList<>(lyRpcServerDtos);
        }
        Map<String, List<LyConfigServerPo>> servers1 = LyConfigServerService.getServers(env);
        servers1.forEach((thisApp, thisServers) -> {
            String thisKey = thisApp + "_" + env;
            List<LyRpcServerDto> thisLyRpcServerDto = thisServers.stream()
                    .map(server ->
                            LyRpcServerDto.builder()
                                    .server("http://" + server.getServerIp() + ":" + server.getServerPort())
                                    .weight(server.getWeight())
                                    .user(server.getUser())
                                    .userPassword(server.getUserPassword())
                                    .build()
                    )
                    .collect(Collectors.toList());
            cache.put(thisKey, thisLyRpcServerDto);
        });
        lyRpcServerDtos = cache.get(app + "_" + env);
        if (CollUtil.isNotEmpty(lyRpcServerDtos)) {
            return new ArrayList<>(lyRpcServerDtos);
        }
        return ListUtil.empty();
    }

    /**
     * 轮询
     *
     * @return
     */
    public static LyRpcServerDto roundRobin(String app, String env) {
        checkServer();
        LyRpcServerDto lyRpcServerDto = null;
        if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), TYPE)) {
            lyRpcServerDto = doRoundRobin(servers);
        }
        if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), TYPE)) {
            if (StrUtil.isBlank(app)) {
                app = servers.get(0).getApp();
                env = servers.get(0).getEnv();
            }
            List<LyRpcServerDto> registerServer = getRegisterServer(app, env);
            if (CollUtil.isNotEmpty(registerServer)) {
                lyRpcServerDto = doRoundRobin(registerServer);
            }
        }
        return lyRpcServerDto;
    }

    /**
     * 执行轮训逻辑
     *
     * @param servers
     * @return
     */
    private static LyRpcServerDto doRoundRobin(List<LyRpcServerDto> servers) {
        LyRpcServerDto lyRpcServerDto = null;
        Lock lock = new ReentrantLock();
        try {
            if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
                try {
                    if (number >= servers.size()) {
                        number = 0;
                    }
                    lyRpcServerDto = servers.get(number);
                    //轮询+1
                    number++;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            log.error("RoundRobin InterruptedException", e);
        }
        if (Objects.isNull(lyRpcServerDto)) {
            lyRpcServerDto = servers.get(0);
        }
        return lyRpcServerDto;
    }

    /**
     * 加权轮询
     *
     * @return
     */
    public static LyRpcServerDto weightRobin(String app, String env) {
        checkServer();
        LyRpcServerDto lyRpcServerDto = null;
        if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), TYPE)) {
            lyRpcServerDto = LyRpcServerDto.builder()
                    .server(doWeightRobin(ipArrayList))
                    .build();
        }
        if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), TYPE)) {
            if (StrUtil.isBlank(app)) {
                app = servers.get(0).getApp();
                env = servers.get(0).getEnv();
            }
            List<LyRpcServerDto> registerServer = getRegisterServer(app, env);
            if (CollUtil.isNotEmpty(registerServer)) {
                List<String> thisIpArrayList = ListUtil.list(false);
                for (LyRpcServerDto server : registerServer) {
                    for (int i = 0; i < server.getWeight(); i++) {
                        thisIpArrayList.add(server.getServer());
                    }
                }
                String server = doWeightRobin(thisIpArrayList);
                LyRpcServerDto lyRpcServerDto1 = registerServer.stream()
                        .filter(register -> Objects.equals(server, register.getServer()))
                        .findAny()
                        .get();
                lyRpcServerDto = LyRpcServerDto.builder()
                        .server(server)
                        .user(lyRpcServerDto1.getUser())
                        .userPassword(lyRpcServerDto1.getUserPassword())
                        .build();
            }
        }
        return lyRpcServerDto;
    }

    /**
     * 执行加权轮训
     *
     * @param ipArrayList
     * @return
     */
    private static String doWeightRobin(List<String> ipArrayList) {
        String serverName = null;
        Lock lock = new ReentrantLock();
        try {
            if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
                try {
                    if (weightNumber >= ipArrayList.size()) {
                        weightNumber = 0;
                    }
                    serverName = ipArrayList.get(weightNumber);
                    //轮询+1
                    weightNumber++;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            log.error("RoundRobin InterruptedException", e);
        }
        if (StringUtils.isBlank(serverName)) {
            serverName = ipArrayList.get(0);
        }
        return serverName;
    }

    /**
     * 随机
     *
     * @return
     */
    public static LyRpcServerDto random(String app, String env) {
        checkServer();
        if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), TYPE)) {
            return servers.get(RandomUtil.randomInt(servers.size()));
        }
        if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), TYPE)) {
            if (StrUtil.isBlank(app)) {
                app = servers.get(0).getApp();
                env = servers.get(0).getEnv();
            }
            List<LyRpcServerDto> registerServer = getRegisterServer(app, env);
            if (CollUtil.isNotEmpty(registerServer)) {
                return registerServer.get(RandomUtil.randomInt(registerServer.size()));
            }
        }
        return null;
    }

    /**
     * 加权随机
     *
     * @return
     */
    public static LyRpcServerDto weightRandom(String app, String env) {
        checkServer();
        if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), TYPE)) {
            return LyRpcServerDto.builder()
                    .server(ipArrayList.get(RandomUtil.randomInt(ipArrayList.size())))
                    .build();
        }
        if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), TYPE)) {
            if (StrUtil.isBlank(app)) {
                app = servers.get(0).getApp();
                env = servers.get(0).getEnv();
            }
            List<LyRpcServerDto> registerServer = getRegisterServer(app, env);
            if (CollUtil.isNotEmpty(registerServer)) {
                List<String> thisIpArrayList = ListUtil.list(false);
                for (LyRpcServerDto server : registerServer) {
                    for (int i = 0; i < server.getWeight(); i++) {
                        thisIpArrayList.add(server.getServer());
                    }
                }
                String server = thisIpArrayList.get(RandomUtil.randomInt(thisIpArrayList.size()));
                LyRpcServerDto lyRpcServerDto1 = registerServer.stream()
                        .filter(register -> Objects.equals(server, register.getServer()))
                        .findAny()
                        .get();
                return LyRpcServerDto.builder()
                        .server(server)
                        .user(lyRpcServerDto1.getUser())
                        .userPassword(lyRpcServerDto1.getUserPassword())
                        .build();
            }
        }
        return null;
    }

    /**
     * ip 哈希
     *
     * @param clientIP
     * @return
     */
    public static LyRpcServerDto ipHash(String app, String env, String clientIP) {
        checkServer();
        if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), TYPE)) {
            int hashCode = clientIP.hashCode();
            int pos = hashCode % servers.size();
            return servers.get(pos);
        }
        if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), TYPE)) {
            if (StrUtil.isBlank(app)) {
                app = servers.get(0).getApp();
                env = servers.get(0).getEnv();
            }
            List<LyRpcServerDto> registerServer = getRegisterServer(app, env);
            if (CollUtil.isNotEmpty(registerServer)) {
                int hashCode = clientIP.hashCode();
                int pos = hashCode % registerServer.size();
                return registerServer.get(pos);
            }
        }
        return null;
    }
}
